package top.v5it.japi.plus.common.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import top.v5it.japi.plus.common.func.FuncTemplate;

import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.function.Function;

/**
 * 服务发现工具类
 *
 * @author zhanpu
 * @date 2021/3/26
 */
@Slf4j
public class ServiceDiscoverUtil {

    public static final String SERVICES_RESOURCE_LOCATION = "META-INF/services/";

    private static final Function<String, String> SERVICES_RESOURCE_LOCATION_FUNC = v -> SERVICES_RESOURCE_LOCATION + v;

    private static final Map<String, MultiValueMap<String, String>> CACHE = new ConcurrentReferenceHashMap<>();

    private ServiceDiscoverUtil() {
        throw new UnsupportedOperationException();
    }

    /**
     * 获取第一个服务
     *
     * @param factoryType 服务类型
     * @param classLoader 类加载器
     * @return 具体服务实现类的全路径
     */
    public static String loadFirstService(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        Assert.notNull(factoryType, "'factoryType' must not be null");
        ClassLoader classLoaderToUse = Optional
                .ofNullable(classLoader)
                .orElseGet(ServiceDiscoverUtil.class::getClassLoader);

        List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);

        FuncTemplate
                .require(log.isTraceEnabled(), () -> log.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames));

        return CollUtil.getFirst(factoryImplementationNames);
    }

    /**
     * 加载所有服务实现类名
     *
     * @param factoryType 服务类型
     * @param classLoader 类加载器
     * @return 具体服务实现类的全路径
     */
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return loadServices(factoryTypeName, classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

    /**
     * 加载所有服务实现类名
     *
     * @param factoryTypeName 服务名
     * @param classLoader     类加载器
     * @return 具体服务实现类的全路径
     */
    private static Map<String, List<String>> loadServices(String factoryTypeName, @Nullable ClassLoader classLoader) {
        final String key = classLoader.getClass().getSimpleName() + "_" + factoryTypeName;
        MultiValueMap<String, String> result = CACHE.get(key);

        if (result != null) {
            return result;
        }

        String servicesResourceLocal = SERVICES_RESOURCE_LOCATION_FUNC.apply(factoryTypeName);
        List<String> listServices;

        try {
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(servicesResourceLocal) :
                    ClassLoader.getSystemResources(servicesResourceLocal));

            result = new LinkedMultiValueMap<>();

            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                listServices = FileUtil.readLines(url, CharsetUtil.CHARSET_UTF_8);

                for (String service : listServices) {
                    result.add(factoryTypeName, service);
                }
            }

            CACHE.put(key, result);
            return result;
        } catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    servicesResourceLocal + "]", ex);
        }
    }
}
